#本文件定义server的会话传输层处理
import message
import base
import socket,select
import struct
class serverso(base.base):
    def __init__(self,s_address,dorecv_who_f,dorecv_f):
        super(serverso,self).__init__()
        self.__ftmsg = message.Ft_message()
        self.__dorecv_who_f =dorecv_who_f
        self.__dorecv_f = dorecv_f
        self._setlogName('ServerSo')
        try:
            self.__so = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.__so.bind(s_address)
            self.__address = s_address
            self.__so.listen()
            self._log('serverso%s is listening...' % str(s_address), self.LOG_INFO)
        except socket.error as e:
            self._log(str(e), self.LOG_WARN)
            return

        # 定义socket列表，用于select监听，包括listen so和accept so，目前读/写/错误一体，一个列表
        self.__sel_solist = []
        self.__sel_solist.append(self.__so)

        #保存id字典，维护用户和链路的关系{id:so}
        self.__iddict = {}

        #保存so字典，维护用户连接需要保存的信息
        # {so:{'raddr':raddr,'leftbys':left_bytes,'id':id}} 为每个连接保存一个left_bytes,用于接收拼接,id存在表明用户已登录，为空表明为野连接
        self.__sodict ={}

    # 定义一些接口用于显示动态数据
    def showsodict(self):  # 显示连接表
        print("here are so diction, key's show has converted to raddr")
        for e in self.__sodict:
            print(self.__sodict.get(e))
        print("total is :%d"%self.__sodict.__len__())

    def showiddict(self):  # 显示id conn交换表member
        print("here are id diction,so's show has converted to raddr")
        #print(self.__iddict)
        for e in self.__iddict:#这里的e为字典的key，不是整个元素
            if self.__iddict.get(e) is not None:
                print('{%d:%s}'%(e,str(self.__iddict.get(e).getpeername())))
            else:
                print('{%d:None}'%e)

        print("total is :%d"%self.__iddict.__len__())

    #以下几个方法提供客户so相关的基本处理
    def __soexceptdo(self,so):
        #此处在sorecv和sosend时可能会发生，但对于同一个so，可能存在重复发生，所以以下处理要避免重复调用的异常

        # 出现异常，这里表明这个客户的四层连接出现异常，处理如下：
        # 删除select表，删除连接表，删除交换表，删除成员表，-----这样做的语义是：底层（四层）连接断了，则上层的连接归零，
        # 接上，目前为：认证过程归零，用户需要重新提交用户名，密码完成认证登录。

        if so in self.__sel_solist:  # 这里加判断，避免下述问题
            self.__sel_solist.remove(so)  # ？？？此处出现so不在list中的异常 有可能是recv时已经异常并删除了，这里又发送再删除时故障
            self._log("so(%s) has been removed from sel_solist " % str(so.getpeername()))
            so.close()#避免重复close放在这里

        # 语义修改如下，连接断，不代表ft用户会话断，因此不删除成员表和idconn，只刷新idconn中的so为none
        #用户会话断对应于用户的取消登录操作,TCP断对应登录用户的idconn表中为空，在查找交换时失败，不能交换
        #TCP断对应无登录用户无影响，是操作TCP模块处理
        if so in self.__sodict.keys():
            id = self.__sodict.get(so).get('id')
            self.__sodict.pop(so)
            self._log("so %s has been popped from sodict." % str(so))
            if id is not None:  # 这里表明该用户已经认证上线，下述表存在
                self.__iddict.update({id:None})
                self._log("%d 's iddict so has been set to none." % id)

        return -1


    def __sosendall(self,so,send_bytes):
        if so is None:
            self._log('__sosendall so is None', self.LOG_ERROR)
            return -0xffffffff

        try:
            r= so.sendall(send_bytes)
            self._log('send data to%s:%s' % (so.getpeername(),send_bytes), self.LOG_DEBUG)
            return 0
        except socket.error as e:
            #这里发生的语义？：在服务器处理so发送的时候，客户端异常断了（包括主动down或者拔除网络线）
            #客户端异常断了，对于服务器存在两种情况，一种是检测到，一种是没检测到，
            # 1. 客户的主动down，会发送fin报文给服务器，服务器收到了，并在应用recv的时候通知，在应用还未处理该通知时，
            # 操作系统TCP状态机可能会完成了down，这样客户调用socksend时就感知到这个异常了，所以这里要处理.
            #2. 客户端异常，拔除网线，这时服务器并不能检测到，服务器的tcp状态机一直正常，而应用调用socketsend，发送会
            #成功，直到tcp发送缓存占满，而后socksend返回异常，这种情况，这里也会感知到。
            #因此这里的处理应该基于以上两种情况判断异常，是为清晰。对于前者，则作为四层连接down处理，删除动态表项，对于
            #后者占满，并不代表错误，所以不能立即删除动态表，需要补充其他的机制最终确定用户down后处理。
            #对于占满的出现使用操作系统socket时候有阻塞和非阻塞处理，方式，阻塞式是得不到的，只会让应用进程暂停，非阻塞式会
            #得到

            #目前为阻塞socket接口调用，故，这里为1的情况，所以删除动态表。对于阻塞对其他连接用户的影响的处理待下一步分析
            #self._log("so(%s) got a exception when send. %s"%(str(so.getpeername()),str(e)),self.LOG_ERROR)
            self._log("socket send exception. %s"%str(e), self.LOG_ERROR)

            return self.__soexceptdo(so)

    def __sorecv(self,so,bufflen):
        if so is None:
            self._log('__sorecv so is None', self.LOG_ERROR)
            return -1
        try:
            r_bytes =  so.recv(bufflen)
            if len(r_bytes) != 0:
                self._log('recv data:%s' %r_bytes,self.LOG_DEBUG)
            else:#select 下，接收长度为0，则表示对方断开连接
                self._log("socket recv len（0）,peer shutdown the connection. %s",self.LOG_DEBUG)
                return self.__soexceptdo(so)
            return r_bytes
        except socket.error as e:
            #self._log("so(%s) got a exception when recv. %s"%(str(so.getpeername()), str(e)), self.LOG_ERROR)
            self._log("socket recv exception. %s" % str(e), self.LOG_ERROR)
            # 出现异常，这里表明这个客户的四层连接出现异常，处理如下：
            # 删除select表，删除连接表，删除交换表，删除成员表，-----这样做的语义是：底层（四层）连接断了，则上层的连接归零，
            # 接上，目前为：认证过程归零，用户需要重新提交用户名，密码完成认证登录。

            return self.__soexceptdo(so)

    def server_run(self):
        #以下执行server run体，循环select并遍历处理收发和error
        while True:
            #wlist 目前无用，不监视，优化
            #rlist, wlist, xlist = select.select(self.__sel_solist, self.__sel_solist, self.__sel_solist)
            rlist, wlist, xlist = select.select(self.__sel_solist, [], self.__sel_solist)
            print("rlist,wlist,xlist:",rlist,wlist,xlist)
            self.__proc_rlist(rlist,wlist)#注意rsolist,wsolist为监听list，rlist,wlist为结果list
            self.__proc_xlist(xlist)

    #该方法处理select后的rlist结果集
    def __proc_rlist(self,rlist,wlist):
        for ro in rlist:#注意：遍历处理中可能引发调用其他so（该so可能是在rlist处理的后续）异常导致so被关闭了，因此在处理中要增加相应保护
            if ro == self.__so:#如果为当前server的侦听sock
                so_c, c_addr = self.__so.accept()
                # 将客户sock分别加入读写list进行监视
                self.__sel_solist.append(so_c)

                self._log('here accept a client: %s.'%str(c_addr))
                # 生成该客户的连接记录并加入连接字典：{so:{'leftbys':left_bytes}}
                self.__sodict.update({so_c: {'raddr':str(so_c.getpeername()),'leftbys':bytes()}})
                self._log('client : %s so, has been added to the connect diction.'%str(c_addr),self.LOG_INFO)

            else:
                # 这里为客户sock，表示客户sock可读
                # 一次最大接收1024字节，若未收完，等待下次处理
                recv_bytes = self.__sorecv(ro,1024)
                #这里会出现对方连接断了，select返回成功，但是recv会成功为0 的情况，现频繁接收，要增加同等，so异常处理
                #if len(recv_bytes) == 0: #改到__sorecv内部判断了
                    #self.__soexceptdo(ro)  #这里相当于检测对方连接断开
                    #break

                if recv_bytes == -1: #so 接收异常
                    break
                # 解析recv bytes，因为tcpsocket为流，故可能recvbytes中可能有多个消息，需要循环截取操作
                # 下面代码还要增加各个字段获取时的长度判断，原因是tcp是流，在每次截取消息时可能会不够长度,
                # 另外如果长度不够则无法进行处理，但是不是代表是错误的，后续的recv可能会补全后面的bytes
                # 因此这里要好好设计，考虑到这里采用消息传送，包含控制消息和用户会话消息在一起，因此可以考虑
                # 按一个消息进行处理，即满足一个消息完整处理则处理，否则记下这个消息（这里需要针对每一个连接设计一个剩余包）
                # ，然后跟下一次的recv拼接，直到它接收完整，才完整处理一次，另外，每次收发recv的长度也要考虑，假如一次未收全，
                # 那还有剩下的会存留在sock缓冲中，等待下一次处理

                # 获取该连接的left_bytes
                left_bytes = self.__sodict.get(ro).get('leftbys')
                if left_bytes is None:#这个属于异常，初始化时已经准备了，不会发生
                    self._log("so(%s)'s leftbys is None."%str(ro.getpeername()), self.LOG_ERROR)
                    break

                print("****************recv_bytes len =%d raddr=%s************"%(recv_bytes.__len__(),ro.getpeername()))
                if len(left_bytes):#???这里出现频繁打印？？？？s
                    self._log("here is a left bytes who's len is not zero, when sock recv.",self.LOG_DEBUG)
                    self._log("leftbytes:%s"%str(left_bytes))
                    #打印该异常的其他关联信息
                    print("ro raddr=%s"%str(ro.getpeername()))
                    if self.__sodict.get(ro) is not None:
                        print("sodict",self.__sodict.get(ro))
                        idtemp = self.__sodict.get(ro).get("id")
                        if idtemp is not None:
                            iddict_item = self.__iddict.get(idtemp)
                            if iddict_item is not None:
                                print("iddict",idtemp,iddict_item)

                #此处可能要优化的函数全局分支，避免每一个循环就要bytes()，除非python系统自己回收，并优化好
                left_bytes = bytes().join([left_bytes, recv_bytes])
                while len(left_bytes) >= 4:#循环处理
                    l, = struct.unpack('!i',left_bytes[:4])
                    if l > len(left_bytes):
                        break
                    id = self.__sodict.get(ro).get('id')
                    if id is None:#表明该链接未绑定用户
                        r_code,r_bytestosend = self.__dorecv_who_f(ro,left_bytes[4:l])
                        if r_code < 0:#异常（包括上送数据异常或未认证或认证未通过）
                            pass
                        if r_bytestosend is not None:
                            l_bytes = struct.pack('!i', struct.calcsize('i') + len(r_bytestosend))
                            self.__sosendall(ro, bytes().join([l_bytes, r_bytestosend]))
                    else:
                        self.__dorecv_f(id,left_bytes[4:l])
                    left_bytes = left_bytes[l:]

                if self.__sodict.get(ro):
                    self.__sodict.get(ro).update({'leftbys':left_bytes})

    def __proc_xlist(self,xlist):
        for xo in xlist:  # 处理异常socket
            self._log("here got a exception client socket,which is %s" % (str(xo)))
            if xo is self.__so:
                self._log("server listening so error. %s",self.LOG_FATAL)
            else:
                self.__soexceptdo(xo)

    #定义提供给应用的发送接口，需要打包成lv的方式
    def sendftmsg(self,sendto_id,ftmsg_bytes):
        #合法性检查
        #so = self.__iddict.get(id) 此行错误代码，id未定义，运行时没有报错
        so = self.__iddict.get(sendto_id)
        if so:
            l_bytes = struct.pack('!i',struct.calcsize('i')+len(ftmsg_bytes))
            send_bytes = bytes().join([l_bytes,ftmsg_bytes])
            return self.__sosendall(so,send_bytes)
        else:
            return -1

    #提供给应用的设置连接是谁的接口
    def soiswho(self,so,who):
        #合法性检查
        dict = self.__sodict.get(so)
        if dict is not None:
            dict.update({'id':who})
            self._log("update {'id':%d} to sodict's so%s itemdict."%(who,str(so.getpeername())),self.LOG_DEBUG)
        self.__iddict.update({who:so})
        self._log("update {%d:so%s} to iddict"%(who,str(so.getpeername())),self.LOG_DEBUG)




